package org.zeromq;

import org.zeromq.ZMQ.Socket;

public class ZThread {
    public static interface IAttachedRunnable {
        public void run(Object[] args, ZContext ctx, Socket pipe);
    }

    public static interface IDetachedRunnable {
        public void run(Object[] args);
    }

    private static class ShimThread extends Thread {
        private ZContext ctx;
        private IAttachedRunnable attachedRunnable;
        private IDetachedRunnable detachedRunnable;
        private Object[] args;
        private Socket pipe;

        protected ShimThread(ZContext ctx, IAttachedRunnable runnable, Object[] args, Socket pipe) {
            assert (ctx != null);
            assert (pipe != null);
            assert (runnable != null);

            this.ctx = ctx;
            this.attachedRunnable = runnable;
            this.args = args;
            this.pipe = pipe;
        }

        public ShimThread(IDetachedRunnable runnable, Object[] args) {
            assert (runnable != null);
            this.detachedRunnable = runnable;
            this.args = args;
        }

        @Override
        public void run() {
            if (attachedRunnable != null) {
                try {
                    attachedRunnable.run(args, ctx, pipe);
                } catch (ZMQException e) {
                    if (e.getErrorCode() != ZMQ.ETERM()) {
                        throw e;
                    }
                }
                ctx.destroy();
            } else
                detachedRunnable.run(args);
        }
    }

    // --------------------------------------------------------------------------
    // Create a detached thread. A detached thread operates autonomously
    // and is used to simulate a separate process. It gets no ctx, and no
    // pipe.

    public static void start(IDetachedRunnable runnable, Object... args) {
        // Prepare child thread
        Thread shim = new ShimThread(runnable, args);
        shim.start();
    }

    // --------------------------------------------------------------------------
    // Create an attached thread. An attached thread gets a ctx and a PAIR
    // pipe back to its parent. It must monitor its pipe, and exit if the
    // pipe becomes unreadable. Returns pipe, or null if there was an error.

    public static Socket fork(ZContext ctx, IAttachedRunnable runnable, Object... args) {
        Socket pipe = ctx.createSocket(ZMQ.PAIR);

        if (pipe != null) {
            pipe.bind(String.format("inproc://zctx-pipe-%d", pipe.hashCode()));
        } else {
            return null;
        }

        // Connect child pipe to our pipe
        ZContext ccontext = ZContext.shadow(ctx);
        Socket cpipe = ccontext.createSocket(ZMQ.PAIR);
        if (cpipe == null)
            return null;
        cpipe.connect(String.format("inproc://zctx-pipe-%d", pipe.hashCode()));

        // Prepare child thread
        Thread shim = new ShimThread(ccontext, runnable, args, cpipe);
        shim.start();

        return pipe;
    }
}
